﻿package {
	
	import flash.display.*;
	import flash.events.*;
	import flash.geom.*;
	import flash.text.*;
	import flash.utils.*;
	
	
	public class WaveMain extends Sprite {
		
		private var m_mCanvasBitmapData:BitmapData;
		private var m_oBitmap:Bitmap;
		private var m_oTextBitmapData:BitmapData;
		private var m_oReflBitmapData:BitmapData;
		private var m_mReflection:MovieClip;
		private var m_mHud:MovieClip;
		private var m_mInstructionText:MovieClip;
		
		private var m_nMouseX:int = 0;
		private var m_nMouseY:int = 0;
		
		private var m_aNodes:Array;
		private var m_nWaveWidth:int = 240;
		private var m_nWaveHeight:int = 180;
		private var m_nWaveTime:Number = 0;
		private var m_nWaveFreq:int = 5;
		private var m_sWaveText:String = "Strille";
		
		private var m_bMouseIsDown:Boolean = false;
		private var m_nShowTextTicks:int = 0;
		private var m_nNextCalcTime:Number = 0;
		
		
		function WaveMain() {
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}
		
		
		private function onAddedToStage(e:Event):void {
			stage.addEventListener(Event.ENTER_FRAME, mainLoop);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onEventMouseMove);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onEventMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, onEventMouseUp);
			stage.addEventListener(Event.RESIZE, onEventStageResize);
			
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			m_oBitmap = new Bitmap();
			this.addChild(m_oBitmap);
			
			m_mHud = new hudMC();
			m_mHud.widthTF.addEventListener(Event.CHANGE, onTextChange);
			m_mHud.heightTF.addEventListener(Event.CHANGE, onTextChange);
			m_mHud.waveFreqTF.addEventListener(Event.CHANGE, onTextChange);
			m_mHud.waveTextTF.addEventListener(Event.CHANGE, onTextChange);
			m_mHud.calcTimeTF.autoSize = TextFieldAutoSize.LEFT;
			this.addChild(m_mHud);
			
			m_mHud.widthTF.text = m_nWaveWidth;
			m_mHud.heightTF.text = m_nWaveHeight;
			m_mHud.waveFreqTF.text = m_nWaveFreq;
			m_mHud.waveTextTF.text = m_sWaveText;

			m_mReflection = new textMC();
			
			m_mInstructionText = new instructionTextMC();
			this.addChild(m_mInstructionText);
			
			init();
		}
		
		
		private function init():void {
			trace("init (width: "+m_nWaveWidth+", height: "+m_nWaveHeight+")");
			
			m_aNodes = new Array();
			
			var x:int;
			var y:int;
			for (y=0;y<=m_nWaveHeight;y++) {
				m_aNodes[y] = new Array();
				for (x=0; x<m_nWaveWidth; x++) {
					m_aNodes[y][x] = new Node();
				}
			}
			
			var oDummyNode:Node = new Node();
			var oUpNode:Node;
			var oDownNode:Node;
			var oLeftNode:Node;
			var oRightNode:Node;
			
			for (y=0; y<m_nWaveHeight; y++) {
				for (x=0; x<m_nWaveWidth; x++) {
					if (y > 0) {
						oUpNode = m_aNodes[y-1][x];
					} else {
						oUpNode = oDummyNode;
					}
					if (y < m_nWaveHeight-1) {
						oDownNode = m_aNodes[y+1][x];
					} else {
						oDownNode = oDummyNode;
					} 
					if (x > 0) {
						oLeftNode = m_aNodes[y][x-1];
					} else {
						oLeftNode = oDummyNode;
					}
					if (x < m_nWaveWidth-1) {
						oRightNode = m_aNodes[y][x+1];
					} else {
						oRightNode = oDummyNode;
					}
					m_aNodes[y][x].setNeighbours(oUpNode, oDownNode, oLeftNode, oRightNode);
				}
			}
			
			if (m_mCanvasBitmapData) {
				m_mCanvasBitmapData.dispose();
			}
			m_mCanvasBitmapData = new BitmapData(m_nWaveWidth, m_nWaveHeight, false, 0);
			m_oBitmap.bitmapData = m_mCanvasBitmapData;
			m_oBitmap.smoothing = true;
			
			if (m_oTextBitmapData) {
				m_oTextBitmapData.dispose();
			}
			m_oTextBitmapData = new BitmapData(m_nWaveWidth, m_nWaveHeight, false, 0);

			if (m_oReflBitmapData) {
				m_oReflBitmapData.dispose();
			}
			m_oReflBitmapData = new BitmapData(m_nWaveWidth, m_nWaveHeight, false, 0);
			
			
			onEventStageResize(null);
		}
		
		
		private function createReflectionAreas(aRectangles:Array):void {
			m_oReflBitmapData.fillRect(m_oReflBitmapData.rect, 0);
			
			for (var n:int=0;n<aRectangles.length;n++) {
				var oRectangle:Rectangle = aRectangles[n];
				var nX:int = (oRectangle.x/stage.stageWidth)*m_nWaveWidth;
				var nY:int = (oRectangle.y/stage.stageHeight)*m_nWaveHeight;
				var nWidth:int = Math.ceil((oRectangle.width/stage.stageWidth)*m_nWaveWidth);
				var nHeight:int = Math.ceil((oRectangle.height/stage.stageHeight)*m_nWaveHeight);
				m_oReflBitmapData.fillRect(new Rectangle(nX, nY, nWidth, nHeight), 0xffffff);
			}
		}

		
		private function mainLoop(e:Event):void {
			var nCalcTime:int = getTimer();
			var q:Node;
			var aNodesY:Array;
			var x:int;
			var y:int;

			if (m_bMouseIsDown) {
				m_nWaveTime += m_nWaveFreq/10;
				var oNodeUnderMouse:Node = m_aNodes[m_nMouseY][m_nMouseX];
				oNodeUnderMouse.in0 = oNodeUnderMouse.in1 = oNodeUnderMouse.in2 = oNodeUnderMouse.in3 = Math.sin(m_nWaveTime)*0.75;
			} else if (Math.random() < 0.05) {
				var nY:int = Math.random()*m_nWaveHeight;
				var nX:int = Math.random()*m_nWaveWidth;
				var oRandomNode:Node = m_aNodes[nY][nX];
				oRandomNode.in0 = oRandomNode.in1 = oRandomNode.in2 = oRandomNode.in3 = 0.25 + Math.random()*0.5;
			}

			if (m_nShowTextTicks-- < 0) {
				m_mReflection.textTF.text = m_sWaveText;
				
				var oDrawMatrix:Matrix = new Matrix();
				oDrawMatrix.translate(m_nWaveWidth/2, m_nWaveHeight/2);
				m_oTextBitmapData.fillRect(m_oTextBitmapData.rect, 0);
				m_oTextBitmapData.draw(m_mReflection, oDrawMatrix);
				for (y=0;y<m_nWaveHeight;y++) {		
					for (x=0;x<m_nWaveWidth;x++) {
						var nPixelBrightness:uint = m_oTextBitmapData.getPixel(x, y) & 0x0000ff;
						if (nPixelBrightness > 0) {
							var nInput:Number = 0.1*(nPixelBrightness/255);
							q = m_aNodes[y][x];
							q.in0 += nInput;
							q.in1 += nInput;
							q.in2 += nInput;
							q.in3 += nInput;
						}
					}
				}
				m_nShowTextTicks = 50 + Math.random()*150;
			}
			
			m_mCanvasBitmapData.lock();
			for (y=0;y<m_nWaveHeight;y++) {		
				aNodesY = m_aNodes[y];
				for (x=0;x<m_nWaveWidth;x++) {
					q = aNodesY[x];
					if (m_oReflBitmapData.getPixel(x, y) == 0) {
						q.calcOut();
					} else {
						q.calcOutRefl();
					}
					var nCol:int = 128 + q.inSum*512;
					if (nCol > 255) {
					  nCol = 255;
					} else if (nCol < 0) {
					  nCol = 0;
					}
					if (nCol > 128) {
						m_mCanvasBitmapData.setPixel(x, y, ((nCol-128)<<16) + ((nCol-128)<<8) + nCol);
					} else {
						m_mCanvasBitmapData.setPixel(x, y, nCol);
					}
				}
			}
			m_mCanvasBitmapData.unlock();
			
			for (y=0; y<m_nWaveHeight; y++) {
				aNodesY = m_aNodes[y];
				for (x=0; x<m_nWaveWidth; x++) {
					q = aNodesY[x];
					q.calcIn();
				}
			}
			
			if (getTimer() > m_nNextCalcTime) {
				m_mHud.calcTimeTF.text = (getTimer() - nCalcTime) + " ms / frame";
				m_mHud.calcTimeTF.x = int(stage.stageWidth - m_mHud.calcTimeTF.width - 5);
				m_mHud.calcTimeTF.y = int(stage.stageHeight - m_mHud.calcTimeTF.height - 5);
				m_nNextCalcTime = getTimer() + 1000;
			}
		}
		
		
		private function onEventMouseMove(e:MouseEvent):void {
			m_nMouseX = Math.max((e.stageX/stage.stageWidth)*m_nWaveWidth, 0);
			m_nMouseY = Math.max((e.stageY/stage.stageHeight)*m_nWaveHeight, 0);
		}

		
		private function onEventMouseDown(e:MouseEvent):void {
			m_bMouseIsDown = true;
			
			if (m_mInstructionText) {
				m_mInstructionText.parent.removeChild(m_mInstructionText);
				m_mInstructionText = null;
			}
		}

		
		private function onEventMouseUp(e:MouseEvent):void {
			m_bMouseIsDown = false;
		}
		
		
		private function onEventStageResize(e:Event):void {
			m_oBitmap.width = stage.stageWidth;
			m_oBitmap.height = stage.stageHeight;
			m_mHud.calcTimeTF.x = int(stage.stageWidth - m_mHud.calcTimeTF.width - 5);
			m_mHud.calcTimeTF.y = int(stage.stageHeight - m_mHud.calcTimeTF.height - 5);
			m_nMouseX = m_nMouseY = 0;
			
			if (m_mInstructionText) {
				m_mInstructionText.x = stage.stageWidth/2;
				m_mInstructionText.y = stage.stageHeight/2;
			}
			
			createReflectionAreas([ m_mHud.widthTF.getBounds(stage),
									m_mHud.heightTF.getBounds(stage),
									m_mHud.waveFreqTF.getBounds(stage),
									m_mHud.waveTextTF.getBounds(stage)]);
		}
		
		
		private function onTextChange(e:Event):void {
			switch(e.target.name) {
				case "widthTF":
					var nNewWidth:int = new Number(e.target.text);
					if (!isNaN(nNewWidth)) {
						m_nWaveWidth = Math.min(Math.max(nNewWidth, 1), 320);
						e.target.text = m_nWaveWidth;
						init();
					}
					break;
				case "heightTF":
					var nNewHeight:int = new Number(e.target.text);
					if (!isNaN(nNewHeight)) {
						m_nWaveHeight = Math.min(Math.max(nNewHeight, 1), 320);
						e.target.text = m_nWaveHeight;
						init();
					}
					break;
				case "waveFreqTF":
					var nNewFreq:Number = parseInt(e.target.text);
					if (!isNaN(nNewFreq)) {
						m_nWaveFreq = Math.min(Math.max(nNewFreq, 1), 10);
						e.target.text = m_nWaveFreq;
					}
					break;
				case "waveTextTF":
					m_sWaveText = e.target.text;
					m_nShowTextTicks = 5;
					break;
			}
		}
	}
}